create or replace package body m4o_stringliteral_advice as

  C_MODULE constant m4o_module := new m4o_module('m4o_stringliteral_advice');

  -- ===========================================================================
  procedure apply_before_body_part(
    i_part in out nocopy m4o_package_generator.part_t)
  is
    v_pc m4o_context := C_MODULE.proc('apply_before_body_part');
    -- -------------------------------------------------------------------------
    -- parser state machine:
    --   outside: 
    --     '        -> inside('   , ' )
    --     q'{      -> inside(q'{ , }')
    --     q'(      -> inside(q'( , )')
    --     q'[      -> inside(q'[ , )')
    --     q'?      -> inside(q'? , ?')
    --     notfound -> exit
    --
    --   inside(beg,end): 
    --     end      -> outside
    --     \\       -> inside(beg,end) , write \
    --     \n       -> inside(beg,end) , write chr(10)
    --     \$       -> inside(beg,end) , write $
    --     ${       -> inside_expr(beg,end)
    --     notfound -> unterminated string literal
    --
    --   inside_expr(beg,end):
    --     }        -> inside(beg,end) , write end||(expr)||beg
    --     notfound -> unterminated string literal expression
    -- -------------------------------------------------------------------------
    procedure apply_to(i_src in out nocopy varchar2)
    is
      v_code    varchar2(32767);
      v_readpos pls_integer := 1;
      -- -----------------------------------------------------------------------
      procedure write      (i_str in varchar2)
      is
      begin
        v_code := v_code||i_str;
      end;
      -- -----------------------------------------------------------------------
      procedure write_upto (i_pos in pls_integer)
      is
      begin
        write(substr(
                i_src,
                v_readpos, 
                i_pos-v_readpos+1));
        v_readpos := i_pos+1;
      end;
      -- -----------------------------------------------------------------------
      procedure inside     (i_beg in varchar2,i_end in varchar2);
      -- -----------------------------------------------------------------------
      procedure inside_expr;
      -- -----------------------------------------------------------------------
      procedure outside
      is
        v_pos1 pls_integer;
        v_pos2 pls_integer;
      begin
        loop
          v_pc.log('outside @'||v_readpos);

          v_pos1 := instr(i_src,'''' ,v_readpos);
          v_pos2 := instr(i_src,'q''',v_readpos);
          
          if v_pos1 > 0
             and (v_pos1 < v_pos2 or v_pos2 = 0) then
            write_upto(v_pos1);
            inside(
              i_beg => '''',
              i_end => '''');
          elsif v_pos2 > 0 then
            write_upto(v_pos2+2);
            inside(
              i_beg => substr(i_src,v_pos2,3),
              i_end => case substr(i_src,v_pos2+2,1)
                       when '{' then '}'
                       when '(' then ')'
                       when '[' then ']'
                       else substr(i_src,v_pos2+2,1)
                       end||
                       '''');
          else
            write_upto(length(i_src));
            exit;
          end if;
        end loop;
      end;
      -- -----------------------------------------------------------------------
      procedure inside(
        i_beg in varchar2,
        i_end in varchar2)
      is
        v_pos1 pls_integer;
        v_pos2 pls_integer;
        v_pos3 pls_integer;
        v_escaped varchar2(1);
      begin
        loop
          v_pc.log('inside @'||v_readpos);

          v_pos1 := instr(i_src,i_end    ,v_readpos);
          v_pos2 := instr(i_src,'\' /*'*/,v_readpos);
          v_pos3 := instr(i_src,'${'     ,v_readpos);

          if v_pos1 > 0
             and (v_pos1 < v_pos2 or v_pos2 = 0)
             and (v_pos1 < v_pos3 or v_pos3 = 0) then
            write_upto(v_pos1+length(i_end));
            exit;
          elsif v_pos2 > 0
                and (v_pos2 < v_pos3 or v_pos3 = 0) then
            write_upto(v_pos2-1);
            v_escaped := substr(i_src,v_pos2+1,1);
            if v_escaped = 'n' then
              write(chr(10));
            else
              write(v_escaped);
            end if;
            v_readpos := v_readpos+2;
          elsif v_pos3 > 0 then
            write_upto(v_pos3-1);
            v_readpos := v_readpos+2;

            write(i_end||'||(');

            inside_expr;

            write(')||'||i_beg);
          else
            v_pc.error('Unterminated literal in "'||
                       i_src||
                       '"');
          end if;
        end loop;
      end;
      -- -----------------------------------------------------------------------
      procedure inside_expr
      is
        v_pos pls_integer;
      begin
        v_pc.log('inside_expr @'||v_readpos);

        v_pos := instr(i_src, '}', v_readpos);

        if v_pos > 0 then
          write_upto(v_pos-1);
          v_readpos := v_readpos+1;
        else
          v_pc.error('Unterminated literal expr at "'||
                       i_src||
                       '..."');
        end if;
      end;
      -- -----------------------------------------------------------------------
    begin
      if i_src is not null then
        v_pc.log(i_src);
        v_code    := null;
        v_readpos := 1;

        outside;

        i_src     := v_code;
      end if;
    end;
    -- -------------------------------------------------------------------------
  begin
    if i_part.attributes.exists(C_DISABLE_ATTR) then
      v_pc.log('not applied because of '||C_DISABLE_ATTR);
    else
      v_pc.do('Apply to src');
      apply_to(i_part.sourcecode);
      v_pc.do('Apply to locals');
      apply_to(i_part.local_declarations);
    end if;
  end;


end;
/


